﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using ParserGenerator.Collections;
using ParserGenerator.Exceptions;
using ParserGenerator.Extensions;
using ParserGenerator.Input;
using ParserGenerator.SyntacticAnalysis;

namespace ParserGenerator.GrammarConstruction
{
    [Serializable]
    public sealed class GrammarTerminalTable : ReadOnlyCollection<GrammarTerminalSymbol>
    {
        public bool AllowAutoDefineLiteralTokens { get; }
        public bool AutoDefineLiteralTokensFrozen { get; private set; }

        [NonSerialized]
        private ReadOnlyDictionary<string, GrammarTerminalSymbol> _namedTokensWrapper;
        private readonly Dictionary<string, GrammarTerminalSymbol> _namedTokens;
        public ReadOnlyDictionary<string, GrammarTerminalSymbol> NamedTokens
        {
            get
            {
                if (_namedTokensWrapper == null)
                    _namedTokensWrapper = _namedTokens.AsReadOnly();
                return _namedTokensWrapper;
            }
        }

        [NonSerialized]
        private ReadOnlyDictionary<string, GrammarTerminalSymbol> _literalTokensWrapper;
        private readonly Dictionary<string, GrammarTerminalSymbol> _literalTokens;
        public ReadOnlyDictionary<string, GrammarTerminalSymbol> LiteralTokens
        {
            get
            {
                if (_literalTokensWrapper == null)
                    _literalTokensWrapper = _literalTokens.AsReadOnly();
                return _literalTokensWrapper;
            }
        }

        [NonSerialized]
        private ReadOnlyCollection<GrammarTerminalSymbol> _endMarkersWrapper;
        private readonly List<GrammarTerminalSymbol> _endMarkers;
        public ReadOnlyCollection<GrammarTerminalSymbol> EndMarkers
        {
            get
            {
                if (_endMarkersWrapper == null)
                    _endMarkersWrapper = _endMarkers.AsReadOnly();
                return _endMarkersWrapper;
            }
        }

        [NonSerialized]
        private ReadOnlyCollection<GrammarTerminalSymbol> _layoutMarkersWrapper;
        private readonly List<GrammarTerminalSymbol> _layoutMarkers;
        public ReadOnlyCollection<GrammarTerminalSymbol> LayoutMarkers
        {
            get
            {
                if (_layoutMarkersWrapper == null)
                    _layoutMarkersWrapper = _layoutMarkers.AsReadOnly();
                return _layoutMarkersWrapper;
            }
        }

        [NonSerialized]
        private ReadOnlyCollection<GrammarTerminalSymbol> _hiddenTokensWrapper;
        private readonly List<GrammarTerminalSymbol> _hiddenTokens;
        public ReadOnlyCollection<GrammarTerminalSymbol> HiddenTokens
        {
            get
            {
                if (_hiddenTokensWrapper == null)
                    _hiddenTokensWrapper = _hiddenTokens.AsReadOnly();
                return _hiddenTokensWrapper;
            }
        }

        [NonSerialized]
        private ReadOnlyCollection<GrammarTerminalSymbol> _keywordsWrapper;
        private readonly List<GrammarTerminalSymbol> _keywords;
        public ReadOnlyCollection<GrammarTerminalSymbol> Keywords
        {
            get
            {
                if (_keywordsWrapper == null)
                    _keywordsWrapper = _keywords.AsReadOnly();
                return _keywordsWrapper;
            }
        }

        [NonSerialized]
        private ReadOnlyCollection<GrammarTerminalSymbol> _reservedWordsWrapper;
        private readonly List<GrammarTerminalSymbol> _reservedWords;
        public ReadOnlyCollection<GrammarTerminalSymbol> ReservedWords
        {
            get
            {
                if (_reservedWordsWrapper == null)
                    _reservedWordsWrapper = _reservedWords.AsReadOnly();
                return _reservedWordsWrapper;
            }
        }

        [NonSerialized]
        private ReadOnlyCollection<GrammarTerminalSymbol> _contextualWordsWrapper;
        private readonly List<GrammarTerminalSymbol> _contextualWords;
        public ReadOnlyCollection<GrammarTerminalSymbol> ContextualWords
        {
            get
            {
                if (_contextualWordsWrapper == null)
                    _contextualWordsWrapper = _contextualWords.AsReadOnly();
                return _contextualWordsWrapper;
            }
        }

        [NonSerialized]
        private ReadOnlyCollection<GrammarTerminalSymbol> _autoDefinedLiteralTokensWrapper;
        private readonly List<GrammarTerminalSymbol> _autoDefinedLiteralTokens;
        public ReadOnlyCollection<GrammarTerminalSymbol> AutoDefinedLiteralTokens
        {
            get
            {
                if (_autoDefinedLiteralTokensWrapper == null)
                    _autoDefinedLiteralTokensWrapper = _autoDefinedLiteralTokens.AsReadOnly();
                return _autoDefinedLiteralTokensWrapper;
            }
        }

        [NonSerialized]
        private ReadOnlySet<GrammarTerminalSymbol> _userRuleTokensWrapper;
        private readonly HashSet<GrammarTerminalSymbol> _userRuleTokens;
        public ReadOnlySet<GrammarTerminalSymbol> UserRuleTokens
        {
            get
            {
                if (_userRuleTokensWrapper == null)
                    _userRuleTokensWrapper = _userRuleTokens.AsReadOnly();
                return _userRuleTokensWrapper;
            }
        }

        public LexicalPrecedenceTable LexicalPrecedences { get; }

        internal GrammarTerminalTable(GrammarDeclaration grammar)
            : base(new List<GrammarTerminalSymbol>())
        {
            if (grammar == null)
                throw new ArgumentNullException(nameof(grammar));
            AllowAutoDefineLiteralTokens = grammar.AllowAutoDefineLiteralTokens;
            _namedTokens = new Dictionary<string, GrammarTerminalSymbol>();
            _literalTokens = new Dictionary<string, GrammarTerminalSymbol>();
            _endMarkers = new List<GrammarTerminalSymbol>();
            _layoutMarkers = new List<GrammarTerminalSymbol>();
            _hiddenTokens = new List<GrammarTerminalSymbol>();
            _keywords = new List<GrammarTerminalSymbol>();
            _reservedWords = new List<GrammarTerminalSymbol>();
            _contextualWords = new List<GrammarTerminalSymbol>();
            _autoDefinedLiteralTokens = new List<GrammarTerminalSymbol>();
            _userRuleTokens = new HashSet<GrammarTerminalSymbol>();
            if (!AnyEndMarkerDefined(grammar))
            {
                Identifier name = Identifier.New(grammar.EndMarkerTokenName);
                StringLiteral text = StringLiteral.New(grammar.EndMarkerTokenText, false);
                TokenDeclarator item = TokenDeclarator.New(name, null, text);
                TokenDeclaration decl = TokenDeclaration.New(TokenDeclarationKind.EndMarker, Sequence.Create(item));
                Define(decl);
            }
            if (grammar.UsePythonLikeIndentation)
            {
                Identifier name1 = Identifier.New(grammar.IndentTokenName);
                Identifier name2 = Identifier.New(grammar.UnindentTokenName);
                Identifier name3 = Identifier.New(grammar.NewLineTokenName);
                StringLiteral text1 = StringLiteral.New(grammar.IndentTokenText);
                StringLiteral text2 = StringLiteral.New(grammar.UnindentTokenText);
                StringLiteral text3 = StringLiteral.New(grammar.NewLineTokenText);
                TokenDeclarator item1 = TokenDeclarator.New(name1, null, text1);
                TokenDeclarator item2 = TokenDeclarator.New(name2, null, text2);
                TokenDeclarator item3 = TokenDeclarator.New(name3, null, text3);
                TokenDeclaration decl = TokenDeclaration.New(TokenDeclarationKind.LayoutMarker, Sequence.Create(item1, item2, item3));
                Define(decl);
            }
            foreach (Directive d in grammar.Directives.Items)
                if (d.IsToken)
                    Define(d.AsToken.Declaration);
            int prec = 0;
            foreach (Directive d in grammar.Directives.Items)
            {
                if (!d.IsOperator)
                    continue;
                switch (d.AsOperator.Declaration.Kind)
                {
                    case OperatorDeclarationKind.Left:
                    case OperatorDeclarationKind.Right:
                    case OperatorDeclarationKind.Nonassociative:
                        prec++;
                        break;
                }
            }
            foreach (Directive d in grammar.Directives.Items)
            {
                if (!d.IsOperator)
                    continue;
                HashSet<GrammarTerminalSymbol> tokens = Resolve(d.AsOperator.Declaration.Items);
                switch (d.AsOperator.Declaration.Kind)
                {
                    case OperatorDeclarationKind.Left:
                        AssignOperatorsToLevel(tokens, prec, OperatorAssociativity.Left);
                        prec--;
                        break;
                    case OperatorDeclarationKind.Right:
                        AssignOperatorsToLevel(tokens, prec, OperatorAssociativity.Right);
                        prec--;
                        break;
                    case OperatorDeclarationKind.Nonassociative:
                        AssignOperatorsToLevel(tokens, prec, OperatorAssociativity.Nonassociative);
                        prec--;
                        break;
                    case OperatorDeclarationKind.Shift:
                        AssignShiftReduce(tokens, ShiftReduceResolution.Shift);
                        break;
                    case OperatorDeclarationKind.Reduce:
                        AssignShiftReduce(tokens, ShiftReduceResolution.Reduce);
                        break;
                }
            }
            LexicalPrecedences = new LexicalPrecedenceTable(grammar, this);
        }

        private static bool AnyEndMarkerDefined(GrammarDeclaration grammar)
        {
            if (grammar == null)
                throw new ArgumentNullException(nameof(grammar));
            foreach (Directive d in grammar.Directives.Items)
                if (d.IsToken && d.AsToken.Declaration.Kind == TokenDeclarationKind.EndMarker)
                    if (d.AsToken.Declaration.Items.Count > 0)
                        return true;
            return false;
        }

        private static void AssignOperatorsToLevel(HashSet<GrammarTerminalSymbol> tokens, int prec, OperatorAssociativity assoc)
        {
            if (tokens == null)
                throw new ArgumentNullException(nameof(tokens));
            foreach (GrammarTerminalSymbol token in tokens)
                token.AssignOperatorLevel(prec, assoc);
        }

        private static void AssignShiftReduce(HashSet<GrammarTerminalSymbol> tokens, ShiftReduceResolution resolution)
        {
            if (tokens == null)
                throw new ArgumentNullException(nameof(tokens));
            foreach (GrammarTerminalSymbol token in tokens)
                token.AssignResolution(resolution);
        }

        private GrammarTerminalSymbol Define(TokenDeclarator n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            if (n.Name == null)
            {
                string value = n.Value.Value;
                return DefineLiteralToken(value, n.Value.Context);
            }
            GrammarTerminalSymbol token;
            if (_namedTokens.TryGetValue(n.Name.Name, out token))
            {
                string message = string.Format(SR.NamedTokenDefined, n.Name.Name.AsSingleQuote());
                NameErrorException e = new NameErrorException(message);
                e.AddLocation(token.Context);
                e.AddLocation(n.Name.Context);
                throw e;
            }
            token = new GrammarTerminalSymbol();
            token.Number = Items.Count;
            token.Name = n.Name.Name;
            token.Context = n.Name.Context;
            token.Text = n.Name.Name;
            token.HasSimpleName = true;
            _namedTokens.Add(token.Name, token);
            Items.Add(token);
            if (n.Text != null)
                token.Text = n.Text.Value;
            if (n.Value != null)
            {
                token.Value = n.Value.Value;
                token.Text = token.Value.AsSingleQuote();
            }
            if (token.Value != null)
            {
                GrammarTerminalSymbol t;
                if (_literalTokens.TryGetValue(token.Value, out t))
                {
                    string message = string.Format(SR.LiteralTokenNameSpecified, token.Text);
                    NameErrorException e = new NameErrorException(message);
                    e.AddLocation(t.Context);
                    e.AddLocation(token.Context);
                    throw e;
                }
                _literalTokens.Add(token.Value, token);
            }
            return token;
        }

        private GrammarTerminalSymbol DefineLiteralToken(string value, SourceContext context)
        {
            if (value == null)
                throw new ArgumentNullException(nameof(value));
            GrammarTerminalSymbol token;
            if (_literalTokens.TryGetValue(value, out token))
            {
                string message = string.Format(SR.LiteralTokenDefined, value.AsSingleQuote());
                NameErrorException e = new NameErrorException(message);
                e.AddLocation(token.Context);
                e.AddLocation(context);
                throw e;
            }
            token = new GrammarTerminalSymbol();
            token.Number = Items.Count;
            token.Name = value.AsSingleQuote();
            token.Context = context;
            token.Text = token.Name;
            token.Value = value;
            _literalTokens.Add(value, token);
            Items.Add(token);
            return token;
        }

        private void Define(TokenDeclaration n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            foreach (TokenDeclarator item in n.Items)
            {
                GrammarTerminalSymbol token = Define(item);
                token.Type = n.AssignedType;
                token.Kind = n.Kind;
                switch (n.Kind)
                {
                    case TokenDeclarationKind.Token:
                        break;
                    case TokenDeclarationKind.EndMarker:
                        _endMarkers.Add(token);
                        break;
                    case TokenDeclarationKind.LayoutMarker:
                        _layoutMarkers.Add(token);
                        break;
                    case TokenDeclarationKind.HiddenToken:
                        _hiddenTokens.Add(token);
                        break;
                    case TokenDeclarationKind.Keyword:
                        _keywords.Add(token);
                        break;
                    case TokenDeclarationKind.ReservedWord:
                        _reservedWords.Add(token);
                        break;
                    case TokenDeclarationKind.ContextualWord:
                        _contextualWords.Add(token);
                        break;
                }
            }
        }

        public GrammarTerminalSymbol LookupNamed(string name)
        {
            if (name == null)
                throw new ArgumentNullException(nameof(name));
            return LookupNamed(name, null);
        }

        public GrammarTerminalSymbol LookupNamed(Identifier name)
        {
            if (name == null)
                throw new ArgumentNullException(nameof(name));
            return LookupNamed(name.Name, name.Context);
        }

        private GrammarTerminalSymbol LookupNamed(string name, SourceContext context)
        {
            if (name == null)
                throw new ArgumentNullException(nameof(name));
            GrammarTerminalSymbol token;
            if (!_namedTokens.TryGetValue(name, out token))
            {
                string message = string.Format(SR.NamedTokenNotDefined, name.AsSingleQuote());
                NameErrorException e = new NameErrorException(message);
                e.AddLocation(context);
                throw e;
            }
            return token;
        }

        public GrammarTerminalSymbol LookupLiteral(string value, SourceContext context)
        {
            if (value == null)
                throw new ArgumentNullException(nameof(value));
            GrammarTerminalSymbol token;
            if (!_literalTokens.TryGetValue(value, out token))
            {
                if (AllowAutoDefineLiteralTokens)
                {
                    if (AutoDefineLiteralTokensFrozen)
                    {
                        string message = string.Format(SR.TerminalTableFrozen, value.AsSingleQuote());
                        NameErrorException e = new NameErrorException(message);
                        e.AddLocation(context);
                        throw e;
                    }
                    token = DefineLiteralToken(value, context);
                    token.IsAutoDefined = true;
                    _autoDefinedLiteralTokens.Add(token);
                }
                else
                {
                    string message = string.Format(SR.LiteralTokenNotDefined, value.AsSingleQuote());
                    NameErrorException e = new NameErrorException(message);
                    e.AddLocation(context);
                    throw e;
                }
            }
            return token;
        }

        public GrammarTerminalSymbol Lookup(TokenReference n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            if (n.IsNamed)
                return LookupNamed(n.AsNamed.Name);
            else
                return LookupLiteral(n.AsLiteral.Value.Value, n.Context);
        }

        public HashSet<GrammarTerminalSymbol> Resolve(Sequence<TokenReference> n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            HashSet<GrammarTerminalSymbol> set = new HashSet<GrammarTerminalSymbol>();
            foreach (TokenReference item in n)
            {
                GrammarTerminalSymbol token = Lookup(item);
                set.Add(token);
            }
            return set;
        }

        internal void AddUserRuleToken(GrammarTerminalSymbol token)
        {
            if (token == null)
                throw new ArgumentNullException(nameof(token));
            _userRuleTokens.Add(token);
        }

        internal LexicalDeclaration CreateLiteralDeclaration()
        {
            AutoDefineLiteralTokensFrozen = true;
            var set = new HashSet<GrammarTerminalSymbol>();
            if (AllowAutoDefineLiteralTokens)
                foreach (var t in _literalTokens)
                    if (!_userRuleTokens.Contains(t.Value))
                        set.Add(t.Value);
            var tokens = new List<GrammarTerminalSymbol>(set);
            tokens.Sort((x, y) =>
            {
                int t = x.Value.Length.CompareTo(y.Value.Length);
                if (t != 0)
                    return -t;
                return x.Value.CompareTo(y.Value);
            });
            var rules = Sequence.GetEmpty<LexicalRuleDeclarator>();
            foreach (GrammarTerminalSymbol token in tokens)
            {
                var value = StringLiteral.New(token.Value, true, token.Context);
                var pattern = LexicalExpression.NewString(value, token.Context);
                var action = SemanticAction.NewFast(Expression.NewQuoteString(value, token.Context));
                var rule = LexicalRuleDeclarator.New(false, pattern, false, action);
                rules = Sequence.Append(rules, rule);
            }
            var owners = LexicalStartReferenceSet.New(Sequence.GetEmpty<Identifier>(), true, false);
            var decl = LexicalStartDeclarator.New(owners, rules);
            var declarators = Sequence.Create(LexicalDeclarator.NewStart(decl));
            var declaration = LexicalDeclaration.New(declarators);
            return declaration;
        }

        private Expression TryGetValueNode(SemanticAction n, out bool isPure)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            if (n.IsFast)
            {
                isPure = true;
                return n.AsFast.Value;
            }
            else
            {
                Statement stmt = n.AsFull.Actions;
                if (stmt.IsSuite)
                    stmt = stmt.AsSuite.Body;
                if (stmt.IsSimple)
                    stmt = stmt.AsSimple.Body;
                Sequence<Statement> actions;
                if (n.AsFull.Actions.IsSequence)
                    actions = n.AsFull.Actions.AsSequence.Items;
                else if (n.AsFull.Actions.IsBlock)
                    actions = n.AsFull.Actions.AsBlock.Body;
                else
                    actions = Sequence.Create(n.AsFull.Actions);
                if (actions.Count <= 0)
                {
                    isPure = false;
                    return null;
                }
                Statement last = actions[actions.Count - 1];
                if (last.IsExpr)
                {
                    Expression e = last.AsExpr.Value;
                    if (e.IsYield && e.AsYield.Value != null)
                    {
                        isPure = (actions.Count == 1);
                        return e.AsYield.Value;
                    }
                }
                isPure = false;
                return null;
            }
        }

        public GrammarTerminalSymbol TryGetToken(LexicalRuleDeclarator rule)
        {
            if (rule == null)
                throw new ArgumentNullException(nameof(rule));
            return TryGetToken(rule, true);
        }

        public GrammarTerminalSymbol TryGetToken(LexicalRuleDeclarator rule, bool requirePure)
        {
            if (rule == null)
                throw new ArgumentNullException(nameof(rule));
            if (rule.Action == null)
                return null;
            bool isPure;
            Expression n = TryGetValueNode(rule.Action, out isPure);
            if (n == null || requirePure && !isPure)
                return null;
            if (n.IsNameReference)
            {
                GrammarTerminalSymbol sym;
                _namedTokens.TryGetValue(n.AsNameReference.Name.Name, out sym);
                return sym;
            }
            if (n.IsQuoteString)
            {
                GrammarTerminalSymbol sym;
                _literalTokens.TryGetValue(n.AsQuoteString.Value.Value, out sym);
                return sym;
            }
            return null;
        }

        public sealed override string ToString()
        {
            return string.Join("\n", Items);
        }
    }
}
